/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.aplink.generic.google.datetimepicker.date;

import java.security.InvalidParameterException;
import java.util.Calendar;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.widget.ExploreByTouchHelper;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;

import com.aplink.generic.google.datetimepicker.Utils;
import com.aplink.generic.google.datetimepicker.date.MonthAdapter.CalendarDay;
import com.aplink.sg.R;

/**
 * A calendar-like view displaying a specified month and the appropriate
 * selectable day numbers within the specified month.
 */
public abstract class MonthView extends View {
    /**
     * Provides a virtual view hierarchy for interfacing with an accessibility
     * service.
     */
    private class MonthViewTouchHelper extends ExploreByTouchHelper {
        private static final String DATE_FORMAT = "dd MMMM yyyy";

        private final Calendar mTempCalendar = Calendar.getInstance();
        private final Rect mTempRect = new Rect();

        public MonthViewTouchHelper(final View host) {
            super(host);
        }

        public void clearFocusedVirtualView() {
            final int focusedVirtualView = getFocusedVirtualView();
            if (focusedVirtualView != ExploreByTouchHelper.INVALID_ID) {
                this.getAccessibilityNodeProvider(MonthView.this)
                        .performAction(
                                focusedVirtualView,
                                AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
                                null);
            }
        }

        /**
         * Calculates the bounding rectangle of a given time object.
         * 
         * @param day
         *            The day to calculate bounds for
         * @param rect
         *            The rectangle in which to store the bounds
         */
        private void getItemBounds(final int day, final Rect rect) {
            final int offsetX = MonthView.this.mPadding;
            final int offsetY = MonthView.MONTH_HEADER_SIZE;
            final int cellHeight = MonthView.this.mRowHeight;
            final int cellWidth = ((MonthView.this.mWidth - (2 * MonthView.this.mPadding)) / MonthView.this.mNumDays);
            final int index = ((day - 1) + findDayOffset());
            final int row = (index / MonthView.this.mNumDays);
            final int column = (index % MonthView.this.mNumDays);
            final int x = (offsetX + (column * cellWidth));
            final int y = (offsetY + (row * cellHeight));

            rect.set(x, y, (x + cellWidth), (y + cellHeight));
        }

        /**
         * Generates a description for a given time object. Since this
         * description will be spoken, the components are ordered by descending
         * specificity as DAY MONTH YEAR.
         * 
         * @param day
         *            The day to generate a description for
         * @return A description of the time object
         */
        private CharSequence getItemDescription(final int day) {
            this.mTempCalendar.set(MonthView.this.mYear, MonthView.this.mMonth,
                    day);
            final CharSequence date = DateFormat.format(
                    MonthViewTouchHelper.DATE_FORMAT,
                    this.mTempCalendar.getTimeInMillis());

            if (day == MonthView.this.mSelectedDay) {
                return getContext().getString(R.string.item_is_selected, date);
            }

            return date;
        }

        @Override
        protected int getVirtualViewAt(final float x, final float y) {
            final int day = getDayFromLocation(x, y);
            if (day >= 0) {
                return day;
            }
            return ExploreByTouchHelper.INVALID_ID;
        }

        @Override
        protected void getVisibleVirtualViews(final List<Integer> virtualViewIds) {
            for (int day = 1; day <= MonthView.this.mNumCells; day++) {
                virtualViewIds.add(day);
            }
        }

        @Override
        protected boolean onPerformActionForVirtualView(
                final int virtualViewId, final int action,
                final Bundle arguments) {
            switch (action) {
            case AccessibilityNodeInfo.ACTION_CLICK:
                onDayClick(virtualViewId);
                return true;
            }

            return false;
        }

        @Override
        protected void onPopulateEventForVirtualView(final int virtualViewId,
                final AccessibilityEvent event) {
            event.setContentDescription(getItemDescription(virtualViewId));
        }

        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
        @Override
        protected void onPopulateNodeForVirtualView(final int virtualViewId,
                final AccessibilityNodeInfoCompat node) {
            getItemBounds(virtualViewId, this.mTempRect);

            node.setContentDescription(getItemDescription(virtualViewId));
            node.setBoundsInParent(this.mTempRect);
            node.addAction(AccessibilityNodeInfo.ACTION_CLICK);

            if (virtualViewId == MonthView.this.mSelectedDay) {
                node.setSelected(true);
            }

        }

        public void setFocusedVirtualView(final int virtualViewId) {
            this.getAccessibilityNodeProvider(MonthView.this).performAction(
                    virtualViewId,
                    AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS,
                    null);
        }
    }

    /**
     * Handles callbacks when the user clicks on a time object.
     */
    public interface OnDayClickListener {
        public void onDayClick(MonthView view, CalendarDay day);
    }

    protected static int DAY_SELECTED_CIRCLE_SIZE;
    protected static int DAY_SEPARATOR_WIDTH = 1;
    protected static final int DEFAULT_FOCUS_MONTH = -1;
    protected static int DEFAULT_HEIGHT = 32;
    protected static final int DEFAULT_NUM_DAYS = 7;
    protected static final int DEFAULT_NUM_ROWS = 6;

    protected static final int DEFAULT_SELECTED_DAY = -1;
    protected static final int DEFAULT_SHOW_WK_NUM = 0;
    protected static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
    protected static final int MAX_NUM_ROWS = 6;
    protected static int MIN_HEIGHT = 10;
    protected static int MINI_DAY_NUMBER_TEXT_SIZE;
    protected static int MONTH_DAY_LABEL_TEXT_SIZE;
    protected static int MONTH_HEADER_SIZE;
    protected static int MONTH_LABEL_TEXT_SIZE;

    // used for scaling to the device density
    protected static float mScale = 0;

    private static final int SELECTED_CIRCLE_ALPHA = 60;
    /**
     * Which month is currently in focus, as defined by {@link Time#month}
     * [0-11].
     */
    public static final String VIEW_PARAMS_FOCUS_MONTH = "focus_month";
    /**
     * These params can be passed into the view to control how it appears.
     * {@link #VIEW_PARAMS_WEEK} is the only required field, though the default
     * values are unlikely to fit most layouts correctly.
     */
    /**
     * This sets the height of this week in pixels
     */
    public static final String VIEW_PARAMS_HEIGHT = "height";
    /**
     * This specifies the position (or weeks since the epoch) of this week,
     * calculated using {@link Utils#getWeeksSinceEpochFromJulianDay}
     */
    public static final String VIEW_PARAMS_MONTH = "month";
    /**
     * How many days to display at a time. Days will be displayed starting with
     * {@link #mWeekStart}.
     */
    public static final String VIEW_PARAMS_NUM_DAYS = "num_days";
    /**
     * This sets one of the days in this view as selected {@link Time#SUNDAY}
     * through {@link Time#SATURDAY}.
     */
    public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day";

    /**
     * If this month should display week numbers. false if 0, true otherwise.
     */
    public static final String VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num";

    /**
     * Which day the week should start on. {@link Time#SUNDAY} through
     * {@link Time#SATURDAY}.
     */
    public static final String VIEW_PARAMS_WEEK_START = "week_start";

    /**
     * This specifies the position (or weeks since the epoch) of this week,
     * calculated using {@link Utils#getWeeksSinceEpochFromJulianDay}
     */
    public static final String VIEW_PARAMS_YEAR = "year";
    private final Calendar mCalendar;

    private final Calendar mDayLabelCalendar;
    private int mDayOfWeekStart = 0;
    private final String mDayOfWeekTypeface;
    protected int mDayTextColor;
    // The Julian day of the first day displayed by this item
    protected int mFirstJulianDay = -1;

    // The month of the first day in this week
    protected int mFirstMonth = -1;
    private final Formatter mFormatter;

    // If this view contains the today
    protected boolean mHasToday = false;
    // The month of the last day in this week
    protected int mLastMonth = -1;
    // Whether to prevent setting the accessibility delegate
    private final boolean mLockAccessibilityDelegate;

    protected int mMonth;

    protected Paint mMonthDayLabelPaint;
    protected Paint mMonthNumPaint;
    protected int mMonthTitleBGColor;
    protected Paint mMonthTitleBGPaint;
    protected int mMonthTitleColor;
    protected Paint mMonthTitlePaint;
    private final String mMonthTitleTypeface;
    // The number of days + a spot for week number if it is displayed
    protected int mNumCells = this.mNumDays;
    // How many days to display
    protected int mNumDays = MonthView.DEFAULT_NUM_DAYS;
    private int mNumRows = MonthView.DEFAULT_NUM_ROWS;
    // Optional listener for handling day click actions
    private OnDayClickListener mOnDayClickListener;

    // affects the padding on the sides of this view
    protected int mPadding = 0;
    // The height this view should draw at in pixels, set by height param
    protected int mRowHeight = MonthView.DEFAULT_HEIGHT;
    protected Paint mSelectedCirclePaint;

    // Which day is selected [0-6] or -1 if no day is selected
    protected int mSelectedDay = -1;

    // The left edge of the selected day
    protected int mSelectedLeft = -1;
    // The right edge of the selected day
    protected int mSelectedRight = -1;

    private final StringBuilder mStringBuilder;
    // Which day is today [0-6] or -1 if no day is today
    protected int mToday = MonthView.DEFAULT_SELECTED_DAY;
    protected int mTodayNumberColor;
    private final MonthViewTouchHelper mTouchHelper;

    // Which day of the week to start on [0-6]
    protected int mWeekStart = MonthView.DEFAULT_WEEK_START;

    // Quick reference to the width of this view, matches parent
    protected int mWidth;

    protected int mYear;

    public MonthView(final Context context) {
        super(context);

        final Resources res = context.getResources();

        this.mDayLabelCalendar = Calendar.getInstance();
        this.mCalendar = Calendar.getInstance();

        this.mDayOfWeekTypeface = res
                .getString(R.string.day_of_week_label_typeface);
        this.mMonthTitleTypeface = res.getString(R.string.sans_serif);

        this.mDayTextColor = res.getColor(R.color.date_picker_text_normal);
        this.mTodayNumberColor = res.getColor(R.color.blue);
        this.mMonthTitleColor = res.getColor(R.color.white);
        this.mMonthTitleBGColor = res.getColor(R.color.circle_background);

        this.mStringBuilder = new StringBuilder(50);
        this.mFormatter = new Formatter(this.mStringBuilder,
                Locale.getDefault());

        MonthView.MINI_DAY_NUMBER_TEXT_SIZE = res
                .getDimensionPixelSize(R.dimen.day_number_size);
        MonthView.MONTH_LABEL_TEXT_SIZE = res
                .getDimensionPixelSize(R.dimen.month_label_size);
        MonthView.MONTH_DAY_LABEL_TEXT_SIZE = res
                .getDimensionPixelSize(R.dimen.month_day_label_text_size);
        MonthView.MONTH_HEADER_SIZE = res
                .getDimensionPixelOffset(R.dimen.month_list_item_header_height);
        MonthView.DAY_SELECTED_CIRCLE_SIZE = res
                .getDimensionPixelSize(R.dimen.day_number_select_circle_radius);

        this.mRowHeight = (res
                .getDimensionPixelOffset(R.dimen.date_picker_view_animator_height) - MonthView.MONTH_HEADER_SIZE)
                / MonthView.MAX_NUM_ROWS;

        // Set up accessibility components.
        this.mTouchHelper = new MonthViewTouchHelper(this);
        ViewCompat.setAccessibilityDelegate(this, this.mTouchHelper);
        ViewCompat.setImportantForAccessibility(this,
                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
        this.mLockAccessibilityDelegate = true;

        // Sets up any standard paints that will be used
        initView();
    }

    private int calculateNumRows() {
        final int offset = findDayOffset();
        final int dividend = (offset + this.mNumCells) / this.mNumDays;
        final int remainder = (offset + this.mNumCells) % this.mNumDays;
        return (dividend + (remainder > 0 ? 1 : 0));
    }

    /**
     * Clears accessibility focus within the view. No-op if the view does not
     * contain accessibility focus.
     */
    public void clearAccessibilityFocus() {
        this.mTouchHelper.clearFocusedVirtualView();
    }

    @Override
    public boolean dispatchHoverEvent(final MotionEvent event) {
        // First right-of-refusal goes the touch exploration helper.
        if (this.mTouchHelper.dispatchHoverEvent(event)) {
            return true;
        }
        return super.dispatchHoverEvent(event);
    }

    /**
     * This method should draw the month day. Implemented by sub-classes to
     * allow customization.
     * 
     * @param canvas
     *            The canvas to draw on
     * @param year
     *            The year of this month day
     * @param month
     *            The month of this month day
     * @param day
     *            The day number of this month day
     * @param x
     *            The default x position to draw the day number
     * @param y
     *            The default y position to draw the day number
     * @param startX
     *            The left boundary of the day number rect
     * @param stopX
     *            The right boundary of the day number rect
     * @param startY
     *            The top boundary of the day number rect
     * @param stopY
     *            The bottom boundary of the day number rect
     */
    public abstract void drawMonthDay(Canvas canvas, int year, int month,
            int day, int x, int y, int startX, int stopX, int startY, int stopY);

    private void drawMonthDayLabels(final Canvas canvas) {
        final int y = MonthView.MONTH_HEADER_SIZE
                - (MonthView.MONTH_DAY_LABEL_TEXT_SIZE / 2);
        final int dayWidthHalf = (this.mWidth - (this.mPadding * 2))
                / (this.mNumDays * 2);

        for (int i = 0; i < this.mNumDays; i++) {
            final int calendarDay = (i + this.mWeekStart) % this.mNumDays;
            final int x = (((2 * i) + 1) * dayWidthHalf) + this.mPadding;
            this.mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
            canvas.drawText(
                    this.mDayLabelCalendar.getDisplayName(Calendar.DAY_OF_WEEK,
                            Calendar.SHORT, Locale.getDefault()).toUpperCase(
                            Locale.getDefault()), x, y,
                    this.mMonthDayLabelPaint);
        }
    }

    /**
     * Draws the week and month day numbers for this week. Override this method
     * if you need different placement.
     * 
     * @param canvas
     *            The canvas to draw on
     */
    protected void drawMonthNums(final Canvas canvas) {
        int y = (((this.mRowHeight + MonthView.MINI_DAY_NUMBER_TEXT_SIZE) / 2) - MonthView.DAY_SEPARATOR_WIDTH)
                + MonthView.MONTH_HEADER_SIZE;
        final int dayWidthHalf = (this.mWidth - (this.mPadding * 2))
                / (this.mNumDays * 2);
        int j = findDayOffset();
        for (int dayNumber = 1; dayNumber <= this.mNumCells; dayNumber++) {
            final int x = (((2 * j) + 1) * dayWidthHalf) + this.mPadding;

            final int yRelativeToDay = ((this.mRowHeight + MonthView.MINI_DAY_NUMBER_TEXT_SIZE) / 2)
                    - MonthView.DAY_SEPARATOR_WIDTH;

            final int startX = x - dayWidthHalf;
            final int stopX = x + dayWidthHalf;
            final int startY = y - yRelativeToDay;
            final int stopY = startY + this.mRowHeight;

            drawMonthDay(canvas, this.mYear, this.mMonth, dayNumber, x, y,
                    startX, stopX, startY, stopY);

            j++;
            if (j == this.mNumDays) {
                j = 0;
                y += this.mRowHeight;
            }
        }
    }

    private void drawMonthTitle(final Canvas canvas) {
        final int x = (this.mWidth + (2 * this.mPadding)) / 2;
        final int y = ((MonthView.MONTH_HEADER_SIZE - MonthView.MONTH_DAY_LABEL_TEXT_SIZE) / 2)
                + (MonthView.MONTH_LABEL_TEXT_SIZE / 3);
        canvas.drawText(getMonthAndYearString(), x, y, this.mMonthTitlePaint);
    }

    private int findDayOffset() {
        return (this.mDayOfWeekStart < this.mWeekStart ? (this.mDayOfWeekStart + this.mNumDays)
                : this.mDayOfWeekStart)
                - this.mWeekStart;
    }

    /**
     * @return The date that has accessibility focus, or {@code null} if no date
     *         has focus
     */
    public CalendarDay getAccessibilityFocus() {
        final int day = this.mTouchHelper.getFocusedVirtualView();
        if (day >= 0) {
            return new CalendarDay(this.mYear, this.mMonth, day);
        }
        return null;
    }

    /**
     * Calculates the day that the given x position is in, accounting for week
     * number. Returns the day or -1 if the position wasn't in a day.
     * 
     * @param x
     *            The x position of the touch event
     * @return The day number, or -1 if the position wasn't in a day
     */
    public int getDayFromLocation(final float x, final float y) {
        final int dayStart = this.mPadding;
        if ((x < dayStart) || (x > (this.mWidth - this.mPadding))) {
            return -1;
        }
        // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
        final int row = (int) (y - MonthView.MONTH_HEADER_SIZE)
                / this.mRowHeight;
        final int column = (int) (((x - dayStart) * this.mNumDays) / (this.mWidth
                - dayStart - this.mPadding));

        int day = (column - findDayOffset()) + 1;
        day += row * this.mNumDays;
        if ((day < 1) || (day > this.mNumCells)) {
            return -1;
        }
        return day;
    }

    private String getMonthAndYearString() {
        final int flags = DateUtils.FORMAT_SHOW_DATE
                | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_NO_MONTH_DAY;
        this.mStringBuilder.setLength(0);
        final long millis = this.mCalendar.getTimeInMillis();
        return DateUtils.formatDateRange(getContext(), this.mFormatter, millis,
                millis, flags, Time.getCurrentTimezone()).toString();
    }

    /**
     * Sets up the text and style properties for painting. Override this if you
     * want to use a different paint.
     */
    protected void initView() {
        this.mMonthTitlePaint = new Paint();
        this.mMonthTitlePaint.setFakeBoldText(true);
        this.mMonthTitlePaint.setAntiAlias(true);
        this.mMonthTitlePaint.setTextSize(MonthView.MONTH_LABEL_TEXT_SIZE);
        this.mMonthTitlePaint.setTypeface(Typeface.create(
                this.mMonthTitleTypeface, Typeface.BOLD));
        this.mMonthTitlePaint.setColor(this.mDayTextColor);
        this.mMonthTitlePaint.setTextAlign(Align.CENTER);
        this.mMonthTitlePaint.setStyle(Style.FILL);

        this.mMonthTitleBGPaint = new Paint();
        this.mMonthTitleBGPaint.setFakeBoldText(true);
        this.mMonthTitleBGPaint.setAntiAlias(true);
        this.mMonthTitleBGPaint.setColor(this.mMonthTitleBGColor);
        this.mMonthTitleBGPaint.setTextAlign(Align.CENTER);
        this.mMonthTitleBGPaint.setStyle(Style.FILL);

        this.mSelectedCirclePaint = new Paint();
        this.mSelectedCirclePaint.setFakeBoldText(true);
        this.mSelectedCirclePaint.setAntiAlias(true);
        this.mSelectedCirclePaint.setColor(this.mTodayNumberColor);
        this.mSelectedCirclePaint.setTextAlign(Align.CENTER);
        this.mSelectedCirclePaint.setStyle(Style.FILL);
        this.mSelectedCirclePaint.setAlpha(MonthView.SELECTED_CIRCLE_ALPHA);

        this.mMonthDayLabelPaint = new Paint();
        this.mMonthDayLabelPaint.setAntiAlias(true);
        this.mMonthDayLabelPaint
                .setTextSize(MonthView.MONTH_DAY_LABEL_TEXT_SIZE);
        this.mMonthDayLabelPaint.setColor(this.mDayTextColor);
        this.mMonthDayLabelPaint.setTypeface(Typeface.create(
                this.mDayOfWeekTypeface, Typeface.NORMAL));
        this.mMonthDayLabelPaint.setStyle(Style.FILL);
        this.mMonthDayLabelPaint.setTextAlign(Align.CENTER);
        this.mMonthDayLabelPaint.setFakeBoldText(true);

        this.mMonthNumPaint = new Paint();
        this.mMonthNumPaint.setAntiAlias(true);
        this.mMonthNumPaint.setTextSize(MonthView.MINI_DAY_NUMBER_TEXT_SIZE);
        this.mMonthNumPaint.setStyle(Style.FILL);
        this.mMonthNumPaint.setTextAlign(Align.CENTER);
        this.mMonthNumPaint.setFakeBoldText(false);
    }

    /**
     * Called when the user clicks on a day. Handles callbacks to the
     * {@link OnDayClickListener} if one is set.
     * 
     * @param day
     *            The day that was clicked
     */
    private void onDayClick(final int day) {
        if (this.mOnDayClickListener != null) {
            this.mOnDayClickListener.onDayClick(this, new CalendarDay(
                    this.mYear, this.mMonth, day));
        }

        // This is a no-op if accessibility is turned off.
        this.mTouchHelper.sendEventForVirtualView(day,
                AccessibilityEvent.TYPE_VIEW_CLICKED);
    }

    @Override
    protected void onDraw(final Canvas canvas) {
        drawMonthTitle(canvas);
        drawMonthDayLabels(canvas);
        drawMonthNums(canvas);
    }

    @Override
    protected void onMeasure(final int widthMeasureSpec,
            final int heightMeasureSpec) {
        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
                (this.mRowHeight * this.mNumRows) + MonthView.MONTH_HEADER_SIZE);
    }

    @Override
    protected void onSizeChanged(final int w, final int h, final int oldw,
            final int oldh) {
        this.mWidth = w;

        // Invalidate cached accessibility information.
        this.mTouchHelper.invalidateRoot();
    }

    @Override
    public boolean onTouchEvent(final MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_UP:
            final int day = getDayFromLocation(event.getX(), event.getY());
            if (day >= 0) {
                onDayClick(day);
            }
            break;
        }
        return true;
    }

    /**
     * Attempts to restore accessibility focus to the specified date.
     * 
     * @param day
     *            The date which should receive focus
     * @return {@code false} if the date is not valid for this month view, or
     *         {@code true} if the date received focus
     */
    public boolean restoreAccessibilityFocus(final CalendarDay day) {
        if ((day.year != this.mYear) || (day.month != this.mMonth)
                || (day.day > this.mNumCells)) {
            return false;
        }
        this.mTouchHelper.setFocusedVirtualView(day.day);
        return true;
    }

    public void reuse() {
        this.mNumRows = MonthView.DEFAULT_NUM_ROWS;
        requestLayout();
    }

    private boolean sameDay(final int day, final Time today) {
        return (this.mYear == today.year) && (this.mMonth == today.month)
                && (day == today.monthDay);
    }

    @Override
    public void setAccessibilityDelegate(final AccessibilityDelegate delegate) {
        // Workaround for a JB MR1 issue where accessibility delegates on
        // top-level ListView items are overwritten.
        if (!this.mLockAccessibilityDelegate) {
            super.setAccessibilityDelegate(delegate);
        }
    }

    /**
     * Sets all the parameters for displaying this week. The only required
     * parameter is the week number. Other parameters have a default value and
     * will only update if a new value is included, except for focus month,
     * which will always default to no focus month if no value is passed in. See
     * {@link #VIEW_PARAMS_HEIGHT} for more info on parameters.
     * 
     * @param params
     *            A map of the new parameters, see {@link #VIEW_PARAMS_HEIGHT}
     */
    public void setMonthParams(final HashMap<String, Integer> params) {
        if (!params.containsKey(MonthView.VIEW_PARAMS_MONTH)
                && !params.containsKey(MonthView.VIEW_PARAMS_YEAR)) {
            throw new InvalidParameterException(
                    "You must specify month and year for this view");
        }
        this.setTag(params);
        // We keep the current value for any params not present
        if (params.containsKey(MonthView.VIEW_PARAMS_HEIGHT)) {
            this.mRowHeight = params.get(MonthView.VIEW_PARAMS_HEIGHT);
            if (this.mRowHeight < MonthView.MIN_HEIGHT) {
                this.mRowHeight = MonthView.MIN_HEIGHT;
            }
        }
        if (params.containsKey(MonthView.VIEW_PARAMS_SELECTED_DAY)) {
            this.mSelectedDay = params.get(MonthView.VIEW_PARAMS_SELECTED_DAY);
        }

        // Allocate space for caching the day numbers and focus values
        this.mMonth = params.get(MonthView.VIEW_PARAMS_MONTH);
        this.mYear = params.get(MonthView.VIEW_PARAMS_YEAR);

        // Figure out what day today is
        final Time today = new Time(Time.getCurrentTimezone());
        today.setToNow();
        this.mHasToday = false;
        this.mToday = -1;

        this.mCalendar.set(Calendar.MONTH, this.mMonth);
        this.mCalendar.set(Calendar.YEAR, this.mYear);
        this.mCalendar.set(Calendar.DAY_OF_MONTH, 1);
        this.mDayOfWeekStart = this.mCalendar.get(Calendar.DAY_OF_WEEK);

        if (params.containsKey(MonthView.VIEW_PARAMS_WEEK_START)) {
            this.mWeekStart = params.get(MonthView.VIEW_PARAMS_WEEK_START);
        } else {
            this.mWeekStart = this.mCalendar.getFirstDayOfWeek();
        }

        this.mNumCells = Utils.getDaysInMonth(this.mMonth, this.mYear);
        for (int i = 0; i < this.mNumCells; i++) {
            final int day = i + 1;
            if (sameDay(day, today)) {
                this.mHasToday = true;
                this.mToday = day;
            }
        }
        this.mNumRows = calculateNumRows();

        // Invalidate cached accessibility information.
        this.mTouchHelper.invalidateRoot();
    }

    public void setOnDayClickListener(final OnDayClickListener listener) {
        this.mOnDayClickListener = listener;
    }
}
